Borland Online And The Cobb Group Present:


August, 1994 - Vol. 1 No. 8

Borland C++ 3.1 Class libraries - Retrieving data with dictionaries and associations

The primary function of a database application is to store and retrieve data. If you're implementing a relatively simple data model, your database application may be able to store this data in records it identifies by looking at one particular part of each record. As it turns out, many applications that aren't primarily database applications could benefit from having some database or lookup table functionality like this built in.

Unfortunately, writing a complete database system inside an application isn't a practical consideration for most programmers. This is particularly true when the application's data is simple and the search criteria are straightforward.

In this article, we'll show you how to use the Association and Dictionary classes from the Borland Container class library to create a simple key-value lookup table. If you're not familiar with the Container class library, see Container class background for more information.

Looking up something in a dictionary

In the Container class library, the Dictionary class defines an unordered set of unique (no duplicates) Association objects. The Association class derives from the Object class, and in turn holds two Object-class-derived items: a key object and a value object.

To use a Dictionary object, you'll first create a group of Association objects from key-value object pairs. Then, you'll add each Association object to the Dictionary object by using its add() member function.

To retrieve any of the value objects (embedded in the Association objects and therefore contained in the Dictionary object), you simply call the Dictionary object's lookup() member function with a key object that matches one of the key objects in the Dictionary object. Figure A shows the relationship between a Dictionary object, the Association objects it holds, and the Association objects' key and value objects.


Figure A - Dictionary class objects hold Association class objects that associate key and value objects.

For example, suppose you want to create a simple lookup table in an application that displays the first line of a song when you supply the song title. You can create each entry using the following format:

String title("Cherish");
String firstLine("Cherish is the word I use...");
Association theAssociation(title, firstLine);

Then, you can add that Association object to the dictionary by using the lines

Dictionary songDictionary;
songDictionary.add(theAssociation); 

To display the first line of the song "Cherish" in songDictionary, you then write

Association& 
song = songDictionary.lookup(String("Cherish"));
cout << song.value() << endl;

Now let's work through a more complete example that demonstrates how you can use Association objects to bind key objects to value objects at runtime. Then, we'll add the objects to a Dictionary object and search for value objects using matching key objects.

A key example

To begin, launch the Borland C++ version 3.1 DOS Integrated Development Environment (IDE). When the IDE's menu bar and desktop appear, choose New from the File menu. When the edit window for the new file appears, enter the code from Listing A.


Listing A: CMDLOOK.CPP

#include <string.h>
#include <strng.h>
#include <assoc.h>
#include <dict.h>
#include <iostream.h>

int main()
{
  char keyBuffer[10];

  cout << "Press a key for the quit command";
  cout << endl;
  cin >> keyBuffer;
  String QuitCommand("Quit Key");
  String QuitKey(keyBuffer);
  Association Quit(QuitKey, QuitCommand);
  Quit.ownsElements(0);

  cout << "Press a key for command #1" << endl;
  cin >> keyBuffer;
  String Cmd1Command("Command #1");
  String Cmd1Key(keyBuffer);
  Association Cmd1(Cmd1Key, Cmd1Command);
  Cmd1.ownsElements(0);

  cout << "Press a key for command #2" << endl;
  cin >> keyBuffer;
  String* Cmd2Command = new String("Command #2");
  String* Cmd2Key = new String(keyBuffer);
  Association Cmd2(*Cmd2Key, *Cmd2Command);

  cout << "Press a key for command #3" << endl;
  cin >> keyBuffer;
  String* Cmd3Command = new String("Command #3");
  String* Cmd3Key = new String(keyBuffer);
  Association Cmd3(*Cmd3Key, *Cmd3Command);

  Dictionary CmdDictionary;
  CmdDictionary.ownsElements(0);
  CmdDictionary.add(Quit);
  CmdDictionary.add(Cmd1);
  CmdDictionary.add(Cmd2);
  CmdDictionary.add(Cmd3);

  String LastKey;
  do
  {
    cout << "Enter a command - ";
    cin >> keyBuffer;

    LastKey = String(keyBuffer);

    Association& command = 
            CmdDictionary.lookup(LastKey);

    if(command != NOOBJECT)
    {
      cout << command.value() << endl;
    }
    else
      cout << "Invalid command!" << endl;
  }
  while(!(LastKey.isEqual(QuitKey)));
  return 0;
}

In this program, we create four Association objects: Quit, Cmd1, Cmd2, and Cmd3. Each Association object holds a String object with the single ASCII character value of the corresponding keyboard key, and another String object that contains a text description of the command. (An Association object can hold String objects because the String class derives from the Object class.)

The constructor for the Association class takes a reference to a key object (the first parameter) and a reference to a value object (the second parameter). As you'll see later, it's important to know this because you'll pass the key and value identifiers only if you create them on the stack (we create Quit and Cmd1 this way). If you create the key and value objects using heap memory (via the new operator), you'll need to dezzzzreference their identifiers in the Association object's constructor (we do this for Cmd2 and Cmd3).

To load the CmdDictionary object, we simply call the add() member function using each of the four Association objects we created earlier. Then, we enter a do-while loop to gather input from the keyboard and put the ASCII value of the key into the LastKey object.

In this loop, we first check to see if the CmdDictionary object contains an Association object for the current command key. If an appropriate object doesn't exist, we display an error message. If a matching object does exist, we display the String object that corresponds to the current command key. The loop will repeat until you press the key that initiates the quit command.

Compiling and running CMDLOOK.EXE

When you finish entering the code, choose Save from the File menu. When the Save As Filename dialog box appears, enter CMDLOOK.CPP in the Save File As entry field and click OK.

Next, choose Application... from the Options menu. In the Set Application Options dialog box, click DOS Standard and then click OK.

To build and run CMDLOOK.EXE, choose Run from the Compile menu. After the compiler finishes building the executable file, the IDE will open a DOS shell and run the application.

When the application starts, it first prompts you to press the keyboard key that will issue the quit command. Enter q as the quit command key and press [Enter].

Next, the application will use a series of similar prompts to ask you for the keyboard keys that correspond to three commands. Enter 1, 2, and 3 as the three command keys.

After you enter the third command key, the application will run a loop that asks you to enter a command key and then displays the name of the corresponding command the application finds in the CmdDictionary object. If you enter a key that isn't one of the three command keys or the quit key, you'll see the following message:

Invalid Command!

Now, press one of the three command keys and then press [Enter]. As you enter various commands, the output from this application should resemble the data shown in Figure B.


Figure B - As you enter various commands, CMDLOOK.EXE displays matching command descriptions from the CmdDictionary object.

Press a key for the quit command
q
Press a key for command #1
1
Press a key for command #2
2
Press a key for command #3
3
Enter a command - 1
Command #1
Enter a command - 3
Command #3
Enter a command - 2
Command #2
Enter a command - 

When you've tried all the commands, enter q to quit CMDLOOK.EXE and return to the IDE. To quit the IDE, choose Quit from the File menu.

Dictionaries, associations, and ownership

By default, Dictionary and Association objects own the objects they hold. This means they assume responsibility for deleting the objects they own unless you tell them otherwise.

This is particularly important in a case like the one we've shown. Because we create some of the String objects and all the Association objects in stack memory (these objects are all local to the main() function), the compiler will call the destructor for each object at the return point of the enclosing function.

Unfortunately, if a Dictionary object owns an Association object that resides on the stack, the Dictionary object will try to delete that Association too. Similarly, if an Association object owns the objects it holds, it will also try to delete those objects. (Calling delete for the same object more than once will usually crash a DOS program or cause a General Protection Fault in a Windows program.)

To avoid this problem, you can tell a Dictionary or Association object that it doesn't own the objects it contains by calling the ownsElements() member function of either class with a value of 0 (zero). This member function­­which these classes inherit from the TShouldDelete class­­allows you to specify the ownership status of a Dictionary or Association object with regard to the items it holds.

By telling the Dictionary or Association object that it doesn't own its elements, you're taking responsibility for deleting these objects at the appropriate time. In our example, this is easy because we create and delete the CmdDictionary object at the same time we create and destroy the command Association objects and some of the String objects. If you're creating anything other than a trivial application like this, you'll probably want to create your Association and key-value objects on the heap.

Conclusion

Many applications need simple lookup table functionality. By learning to use the Dictionary and Association classes from Borland's Container class library, you can add this capability without writing the code from scratch.

Return to the Borland C++ Developer's Journal index

Subscribe to the Borland C++ Developer's Journal


Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.